package com.colloquial.arithcode2;

import java.io.IOException;
import java.math.*;

public class CompresorAritmetico {
	
	private TablaProbabilidades tabla;
	private BitArray  piso ;
	private BitArray techo;
	private BitArray rango;
	private BitArrayStream output;
	private int underflow;
	private int overflow;
	
	
	public CompresorAritmetico() throws Exception{
		this.piso = new BitArray();
		this.piso.SetPiso();
		this.overflow = 0;
		this.underflow = 0;
		this.techo = new BitArray();
		this.techo.SetTecho();
		this.rango = new BitArray();
		this.rango.SetNumero((this.techo.GetNumero() - this.piso.GetNumero()));
		this.output = new BitArrayStream();
	}
	

	public TablaProbabilidades getTabla() {
		return tabla;
	}


	public BitArray getPiso() {
		return piso;
	}


	public BitArray getTecho() {
		return techo;
	}


	public BitArray getRango() {
		return rango;
	}


	public BitArrayStream getOutput() {
		return output;
	}


	public int getUnderflow() {
		return underflow;
	}


	public int getOverflow() {
		return overflow;
	}
/*
 * El Arraylist tiene q tener solo el float con las probabilidades, NO las frecuencias
 * Si es el eof emito el piso directamente, y la probabilidad es la de la ultioma pos del Arraylist
 * Si es el de escape , es igual a cualquier otro caracter pero la prob es el anteultimo del arraylist
 * el rs va de la 0 a la 65537, la 65536 es escape y 65537  el eof
 */

	public void calcularIntervalo(TablaProbabilidades TablaP,int pos ) throws Exception{//este paramentro es el caracter que quiere comprimir
		this.tabla = TablaP;
		int it =0;
		long res = 0;
		long contador = 0;
		boolean encontrado = false;
		while (!encontrado){
			if (pos != it){
				//res = (long) (this.piso.GetNumero() + (this.tabla.GetSimbolo(it).getProbabilidad()/this.tabla.getTotal()) * this.rango.GetNumero());
				contador = contador + this.tabla.GetSimbolo(it).getProbabilidad();
				//this.piso.SetNumero(res); 
				it++;
			}else{
			    long aux = (long) (this.piso.GetNumero() + (contador *(double) (this.rango.GetNumero()+1))/this.tabla.getTotal());
				res = (long) (this.piso.GetNumero() + (((contador + this.tabla.GetSimbolo(it).getProbabilidad())* (this.rango.GetNumero()+1)/this.tabla.getTotal()) )-1);
				encontrado = true;
				this.piso.SetNumero(aux);
				this.techo.SetNumero(res);	
			}
		}
		if(pos != 65537){
			System.out.println("Al comprimir es asi");
			System.out.println("El piso es"+this.piso.GetNumero());
			System.out.println("El piso es"+this.piso.ToBinaryString());	
			System.out.println("El techo es"+this.techo.GetNumero());
			System.out.println("El techo es"+this.techo.ToBinaryString());	
			analizarOutput();//deberia encontrar si o si
			this.rango.SetNumero((this.techo.GetNumero() - this.piso.GetNumero()));	
			System.out.println("La emision hasta aca es   "+this.output.ToBinaryString());
		}else emitirPiso();
		
	}
	
	private void emitirPiso() throws BitArrayException {
		boolean aux;
		System.out.println("EMITO EL PISO ENTERO  " +
				"El piso es"+this.piso.ToBinaryString());
		System.out.println("El Underflow es "+this.underflow);
		for (int i = 0; i < this.techo.Length(); i++) {
			try {
				this.output.PutBit(this.techo.BitAt(i));
				for(int j = this.underflow; j > 0; j--){//emito tantos caracteres inversos como uf tenia
					aux = techo.BitAt(0);
					this.output.PutBit(!aux);
				}
				this.underflow=0;
				
			} catch (BitArrayException e) {
				
				e.printStackTrace();
			}
		}
	}


	public void analizarOutput () throws IOException{
		
		try {
			this.overflow = overflow(); //emito lo q corresponde
			if ( this.overflow > 0){ 
				System.out.println("El overflow es "+this.overflow);
				//of = true en param 3
				//param 4 con q completar el array
				this.piso = BitArray.CompletarBits(this.overflow, this.piso,true ,false ); //completo
				this.techo = BitArray.CompletarBits(this.overflow, this.techo,true ,true );	
				this.overflow = 0;
			}
			this.underflow = underflow();//reviso el underflow y sumo el acumulador
			if(this.underflow > 0){//hay underflow, los cuento para luego eliminar y saber cuanto emitir la proxima vez
				System.out.println("El underflow es "+this.underflow);
				this.piso = BitArray.CompletarBits(this.underflow, this.piso,false ,false ); //completo
				this.techo = BitArray.CompletarBits(this.underflow, this.techo,false ,true );	
			}
		} catch (Exception e) {
		
			e.printStackTrace();
		}

	}
	

	private int underflow() throws Exception {
		int uf = 0;
		boolean iguales;
		if( this.piso.BitAt(0) == this.techo.BitAt(0)) System.out.println("ERROR EN UN UF");
		boolean bpiso = this.piso.BitAt(1);//leo desde el 2 porq los primeros ya se q son distintos porq no hay overflow o si hubo ya se corto
		boolean bpisoSig = false;
		boolean btechoSig = false;
		boolean btecho = this.techo.BitAt(1);
		boolean biento = true;
		iguales = bpiso == btecho;
		int i=2;
		while (iguales == false && biento && i < this.piso.Length()){
			uf++;
			bpisoSig = this.piso.BitAt(i);
			btechoSig = this.techo.BitAt(i);
			iguales = bpiso == btecho;
			i++;
			if (!( bpiso == bpisoSig && btecho ==btechoSig)) biento = false ;
			}
		return uf;
	}

	private int overflow() throws Exception {
		int of = 0;
		int j = 0;
		boolean iguales;
		boolean aux = false;
		boolean bpiso = this.piso.BitAt(j);
		boolean btecho = this.techo.BitAt(j);
		iguales = bpiso == btecho;
		while (iguales == true && j < this.piso.Length()){//tengo q ver el contador de uf q tenia para ver si emito algo inverso
			of++;
			this.output.PutBit(piso.BitAt(j));
			for(int i = this.underflow; i > 0; i--){//emito tantos caracteres inversos como uf tenia
				aux = piso.BitAt(j);
				this.output.PutBit(!aux);
			}
			this.underflow=0;
			j++;
			iguales = this.piso.BitAt(j) == this.techo.BitAt(j);
			}
		return of;
		
	}
}




